package com.gitee.qdbp.jdbc.support;

import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.jdbc.exception.DbErrorCode;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

/**
 * 简化配置的DruidDataSource, 切换数据库只需要修改一行配置(jdbc.xxx)<br>
 * 配置文件应分为两部分, 一是需要开发评估才能决定修改的, 二是运维人员即可修改的<br>
 * 关于数据库的配置, 用户名/密码/访问地址/数据库名是运维的事, 其他的协议类型/各种参数是开发的事<br>
 * 运维人员的这部分配置文件应该尽量集中到一个文件中<br>
 * <br>
 * 密码支持两种配置方式, 第1种是明文密码, 第2种是加密密码<br>
 * dbtype:username:password@address/dbname:schema // 明文密码<br>
 * dbtype:username@address/dbname:schema?EncryptedPassword // 加密密码<br>
 * 数据库密码加密: java -cp qdbp-able.jar com.gitee.qdbp.tools.crypto.GlobalCipherTools db password<br>
 * config第1段是dbtype.subtype, subtype可以没有. 例如mysql, mysql.8, oracle, oracle.sid<br>
 * 通过dbtype.subtype从properties之中自动查找jdbc.url/jdbc.params/jdbc.driver/jdbc.testquery<br>
 * 查找时优先查找jdbc.url.dbtype.subtype, 如果没有再查找jdbc.url.dbtype, 最后查找jdbc.url<br>
 * <pre>
## MySQL
jdbc.xxx = mysql:username:password@address/dbname
## MySQL(8.0的参数不同)
jdbc.xxx = mysql.8:username:password@address/dbname
## MySQL(加密的密码放在最后)
jdbc.xxx = mysql:username@address/dbname?EncryptedPassword
## MariaDB
# jdbc.xxx = mariadb:username:password@127.0.0.1:3306/dbname
## Oracle-DbName
# jdbc.xxx = oracle:username:password@127.0.0.1:1521/dbname
## Oracle-SID
# jdbc.xxx = oracle.sid@127.0.0.1:1521/DbSid
## Oracle-TNS
# jdbc.xxx = oracle.tns@~/TnsName
## DB2 需要schema
# jdbc.xxx = db2:username:password@127.0.0.1:50000/dbname:schema
## H2 嵌入式(不需要ip:port)
# jdbc.xxx = h2.embed:username:password@~/dbname
## H2 TCP模式
# jdbc.xxx = h2.tcp:username:password@127.0.0.1:8082/dbname
## H2 内存模式(不需要username/password/ip:port)
# jdbc.xxx = h2.mem@~/dbname
## SqlServer
# jdbc.xxx = sqlserver:username:password@127.0.0.1:1433/dbname
## SqlServer 老版本
# jdbc.xxx = sqlserver.2000:username:password@127.0.0.1:1433/dbname
## PostgreSQL
# jdbc.xxx = postgresql:username:password@127.0.0.1:5432/dbname:schema
## SQLite 内存模式
# jdbc.xxx = sqlite.mem@~/dbname
## SQLite 文件模式(windows)
# jdbc.xxx = sqlite.file@~/F:/sqlite/main.db
## SQLite 文件模式(linux)
# jdbc.xxx = sqlite.file@~/home/sqlite/main.db
## SQLite 类路径中的文件
# jdbc.xxx = sqlite.res@~/settings/sqlite/main.db

&lt;bean id="setting" class="org.springframework.beans.factory.config.PropertiesFactoryBean"&gt;
    &lt;property  name="fileEncoding" value="UTF-8" /&gt;
    &lt;property name="locations"&gt;
        &lt;list&gt;
            &lt;value&gt;classpath:settings/jdbc/datasource.properties&lt;/value&gt;
            &lt;value&gt;classpath:settings/jdbc/qdbc.propertiess&lt;/value&gt;
            &lt;value&gt;classpath:setting.properties&lt;/value&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;
&lt;bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"&gt;
    &lt;property name="properties" ref="setting" /&gt;
&lt;/bean&gt;
&lt;bean class="com.gitee.qdbp.jdbc.support.AutoHikariDataSource" destroy-method="close"&gt;
    &lt;property name="properties" ref="setting" /&gt;
    &lt;property name="config" value="${jdbc.xxx}" /&gt;
    &lt;property name="url" value="auto" /&gt;
    &lt;property name="driverClassName" value="auto" /&gt;
    &lt;property name="validationQuery" value="auto" /&gt;
&lt;/bean&gt;
 * </pre>
 * 
 * @author zhaohuihua
 * @version 20210503
 */
public class AutoHikariDataSource extends HikariDataSource {

    private static final Logger log = LoggerFactory.getLogger(AutoHikariDataSource.class);

    private Properties properties;
    private String dbconfig;
    private String urlKey;

    public AutoHikariDataSource() {
        super();
    }

    public AutoHikariDataSource(HikariConfig configuration) {
        super(configuration);
    }

    @Override
    public void validate() {
        this.autoConfig();
        super.validate();
    }

    private DataSourceConfig dataSourceConfig;

    protected void autoConfig() {
        if (this.properties == null) {
            this.properties = AutoDataSourceTools.loadDefaultProperties();
        }
        String configString = this.dbconfig;
        if (configString == null && this.urlKey != null) {
            configString = properties.getProperty(this.urlKey);
        }
        if (configString == null) {
            throw new IllegalArgumentException("Missing argument for configString");
        }
        if (dataSourceConfig == null || properties != dataSourceConfig.getProperties()) {
            dataSourceConfig = new DataSourceConfig(properties);
        }
        // 解析configString
        AutoDataSourceTools.parseConfigString(configString, dataSourceConfig);
        super.setUsername(dataSourceConfig.getUserName());
        if (dataSourceConfig.getPassword() != null) {
            super.setPassword(new String(dataSourceConfig.getPassword()));
        }
        // 根据配置自动推断jdbcUrl/params/driver
        String jdbcUrl = dataSourceConfig.findPropertyUseSuffix("jdbc.url", true, true);
        String params = dataSourceConfig.findPropertyUseSuffix("jdbc.params", true, false);
        String driver = dataSourceConfig.findPropertyUseSuffix("jdbc.driver", false, false);
        if (params != null && params.length() > 0) {
            super.setJdbcUrl(jdbcUrl + params);
        } else {
            super.setJdbcUrl(jdbcUrl);
        }
        if (driver != null && driver.length() > 0) {
            super.setDriverClassName(driver);
        } else {
            // 处理特殊的Driver
            String newDriver = AutoDataSourceTools.resolveSpecialDriver(jdbcUrl);
            if (newDriver != null && newDriver.length() > 0) {
                super.setDriverClassName(newDriver);
            }
        }

        // 根据配置自动推断ValidationQuery
        String validationQuery = dataSourceConfig.findPropertyUseSuffix("jdbc.testquery", false, false);
        super.setConnectionTestQuery(validationQuery);
    }

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        // 查找默认配置文件
        Properties defaults = AutoDataSourceTools.loadDefaultProperties();
        if (properties != null) {
            defaults.putAll(properties);
        }
        this.properties = defaults;
    }

    /**
     * 配置JDBC用户名密码连接地址数据库名称<br>
     * 密码支持两种配置方式, 第1种是明文密码, 第2种是加密密码<br>
     * dbtype:username:password@address/dbname:schema // 明文密码<br>
     * dbtype:username@address/dbname:schema?EncryptedPassword // 加密密码<br>
     * 
     * @param config JDBC配置字符串
     */
    public void setConfig(String config) {
        this.dbconfig = config;
    }

    /** JDBC配置字符串 **/
    public String getConfig() {
        return dbconfig;
    }

    /** JDBC配置字符串在Properties中的key **/
    public String getUrlKey() {
        return urlKey;
    }

    /** JDBC配置字符串在Properties中的key **/
    public void setUrlKey(String urlKey) {
        this.urlKey = urlKey;
    }

    @Override
    public void setJdbcUrl(String url) {
        if (!"auto".equalsIgnoreCase(url)) {
            log.warn("Method[setJdbcUrl] not supported, we will auto find content from the properties");
        }
    }

    @Override
    public void setUsername(String username) {
        if (!"auto".equalsIgnoreCase(username)) {
            log.warn("Method[setUsername] not supported, we will auto parse from the field of 'config'");
        }
    }

    @Override
    public void setPassword(String password) {
        if (!"auto".equalsIgnoreCase(password)) {
            log.warn("Method[setPassword] not supported, we will auto parse from the field of 'config'");
        }
    }

    @Override
    public void setDriverClassName(String driverClassName) {
        if (!"auto".equalsIgnoreCase(driverClassName)) {
            log.warn("Method[setDriverClassName] not supported, we will auto find content from the properties");
        }
    }

    @Override
    public void setConnectionTestQuery(String connectionTestQuery) {
        if (!"auto".equalsIgnoreCase(connectionTestQuery)) {
            log.warn("Method[setConnectionTestQuery] not supported, we will auto find content from the properties");
        }
    }

    /**
     * 根据单行url参数构造默认的数据源<br>
     * 只能自定义username,password,address, 其他参数都使用默认值
     * 
     * @param jdbcUrl 数据库连接地址单行url参数<br>
     *            如 mysql:username:password@127.0.0.1:3306/dbname<br>
     *            如 oracle:username:password@127.0.0.1:1521/orcl<br>
     *            如 db2:username:password@127.0.0.1:50000/dbname:schema<br>
     * @return 数据源
     * @since 3.4.0
     */
    public static AutoHikariDataSource buildWith(String jdbcUrl) {
        AutoHikariDataSource datasource = new AutoHikariDataSource();
        datasource.setConfig(jdbcUrl);
        try {
            datasource.validate();
            return datasource;
        } catch (Exception e) {
            datasource.close();
            throw new ServiceException(DbErrorCode.DB_DATA_SOURCE_INIT_ERROR, e);
        }
    }

    /**
     * 根据配置信息构造数据源
     * 
     * @param properties 配置信息
     * @param urlKey URL在Properties中的key<br>
     *            如 properties中配置了jdbc.sys=mysql:username:password@127.0.0.1:3306/dbname<br>
     *            则 urlKey应为jdbc.sys
     * @return 数据源
     * @since 3.4.0
     */
    public static AutoHikariDataSource buildWith(Properties properties, String urlKey) {
        AutoHikariDataSource datasource = new AutoHikariDataSource();
        datasource.setProperties(properties);
        datasource.setUrlKey(urlKey);
        try {
            datasource.validate();
            return datasource;
        } catch (Exception e) {
            datasource.close();
            throw new ServiceException(DbErrorCode.DB_DATA_SOURCE_INIT_ERROR, e);
        }
    }
}
